package AbstractMapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.opencyc.cycobject.CycConstant;
import org.opencyc.cycobject.CycList;
import org.opencyc.util.Log;

/**
 * Interfaces with user and controls analogy objects and their output.
 * 
 * @author Nic Di Noia - nik.ddn@gmail.com
 *
 */
public class AbstractMappingController {
	private BufferedReader stdin;
	private CycConnection cycC;
	private boolean silent = false;// to get clean output	

	public AbstractMappingController()
	{		
		// create log, GUI, and connection
		System.setProperty("org.opencyc.util.log", "file");// tell the log to write to a file
		Log.makeLog("AbstractMappingEngines.log");
		cycC = new CycConnection();
		stdin = new BufferedReader(new InputStreamReader(
				System.in));
	}

	public static void main(String[] args) {
		// start abstract mapping tasks
		long globStartTime, globEndTime;
		globStartTime = System.currentTimeMillis(); 
		AbstractMappingController absMap = new AbstractMappingController();		
		absMap.AbstractMappingInteraction();				
		
		globEndTime = System.currentTimeMillis();
		Log.current.println("Total Time elapsed: "+ (globEndTime - globStartTime)+"ms");
		System.out.println("Quitting...");
		Log.current.close();
		
		// kill all threads
		System.exit(0);
	}

	private void AbstractMappingInteraction()
	{
		try {
			while (true) {
				if (!silent)
				{
					System.out.println("\nEnter a number for a query type: ('exit' to end program, 'verbose' to see cyc-api calls)" +
							"\n1	Find CYC constant" +
							"\n2	Find first level genls of constant." +
							"\n3	Find first level isas of constant." +
							"\n4	Find all genls of constant." +
							"\n5	Find all genlsPreds of constant." +
							"\n6	Find all isa of constant." +
							"\n7	Find all DepSpec of constant." +
							"\n8	Find all SpecInv of constant." +						
							"\n9	Find comments in a constant." +
							"\n10	Calculate number of Category levels beneath constant." +
							"\n11	Set criteria to use in sorting mapping score results.(currently set to " + cycC.getSortBy() +")"+
							"\n12	Calculate Categorical Similarity scores between two concepts." +
							"\n13	Calculate Categorical Similarity scores for all possible pair mappings within an Analog." +						
							"\n14	Generate Analogy Mapping using Similarity scores. (1-to-1 mappings between two equal-sized analogs)" +//TODO add switch between catsim and vsmrel
							"\n15	Answer SAT style multiple choice analogy question using Categorical Similarity Mapping. (FIRST:SECOND::__:__)" + //TODO add switch between catsim and vsmrel
							"\n16	Generate Analogy Mapping using Direct Structure from analog-internal Similarity. (SMT systematicity-like principle)"+
							"\n17	Set concept expansion level.(currently set to " + cycC.getExpandBy() +")"+
							"\n18	Toggle scores to compute: all or sort-determined.(currently set to " + cycC.getRequiredScore() +")"+
							"\n19	Calculate Relational Similarity scores between two concepts.");
				}
				System.out.print("> ");
				String userCommand = stdin.readLine();
				if (userCommand.equals("exit"))					
					break;
				else if (userCommand.equals("verbose"))					
				{
					cycC.toggleVerbose();
					System.out.print("Verbose set to "+ cycC.toggleVerb);
					continue;
				}

				int demoNbr = 0;				
				try {
					demoNbr = Integer.parseInt(userCommand);
				}
				catch (NumberFormatException e) {
					System.out.println("Not a valid instruction. Enter a number from 1-18 inclusive.");
					continue;
				}
					
				try{
					
					long startTime = System.currentTimeMillis(); 
					switch (demoNbr) {
					case 1:
						demo1(cycC,stdin);
						break;
					case 2:
						demo2(cycC,stdin);
						break;
					case 3:
						demo3(cycC,stdin);
						break;
					case 4:
						demo4(cycC,stdin);
						break;
					case 5:
						demo5(cycC,stdin);
						break;
					case 6:
						demo6(cycC,stdin);
						break;
					case 7:
						demo7(cycC,stdin);
						break;
					case 8:
						demo8(cycC,stdin);
						break;
					case 9:
						demo9(cycC,stdin);
						break;
					case 10:
						demo10(cycC,stdin);
						break;
					case 11:
						demo11(cycC,stdin);
						break;
					case 12:
						demo12(cycC,stdin);
						break;
					case 13:
						demo13(cycC,stdin);
						break;
					case 14:
						demo14(cycC,stdin);
						break;
					case 15:
						demo15(cycC,stdin);
						break;
					case 16:
						demo16(cycC,stdin);
						break;
					case 17:
						demo17(cycC,stdin);
						break;
					case 18:
						demo18(cycC,stdin);
						break;
					case 19:
						demo19(cycC,stdin);
						break;
					default:
						System.out.println("Not a valid instruction number");
					}
					long endTime = System.currentTimeMillis();
					Log.current.println("\nTime elapsed: "+ (endTime - startTime)+"ms\n*********************************************************************");
				}
				catch (IOException e)
				{
					System.out.println("IO ERROR!!");
				}


			}
		} catch (Exception e) {
			Log.current.println("Program crashed!");
			Log.current.errorPrintln(e.getMessage());
			Log.current.printStackTrace(e);
		}
	}

	private boolean isValidSyntax(String userCommand)
	{
		try {
			CycConnection.checkConstant(userCommand);
			return true;
		} catch (InvalidCycConstantException e) {
			System.out.println("Invalid constant format.");
			userCommand = "";
			return false;
		}
	}
	private CycConstant findC(CycConnection cycC, String userCommand) throws IOException
	{
		if (!userCommand.equals("")) {
							
			System.out.println("Searching for '" + userCommand + "'.");
			
			if (cycC.findConstant(userCommand) != null) {
				System.out.println("Found:");
				return cycC.getConstant(userCommand);	//accurate spelling				
			} else if (cycC.findCaseCollition(userCommand) != null) {				
				System.out.println("Found with alternate spelling:");
				return cycC.findCaseCollition(userCommand); //alternate spellings				
			} else if (!cycC.getDenotsOfString(userCommand).isEmpty()) {
				CycList denots = cycC.getDenotsOfString(userCommand);				
				System.out.println("Finding alternate denotation: " + denots);
				
				CycList allConstants = new CycList(denots.getReferencedConstants());//deep search for all constants (within cyclists, forts or narts)
				
				if (allConstants.size() == 1)
				{
					System.out.println("Alternate denotation: " + allConstants.get(0));				
					return (CycConstant) allConstants.get(0);//since only one denotation, return that
				}//else multiple denotations exist, ask user to choose

				System.out.println("*** No simple alternate denotation found for " + userCommand +"! - Choose constant:");
				for (int i = 0; i < allConstants.size(); i++)				
					System.out.println(i+1+" :> "+ allConstants.get(i));
				System.out.println("Enter Number for Constant :");
				
				int index = -1;				
				while ((index < 0) || (index >= allConstants.size())) 
				{		
					System.out.print("> ");
					userCommand = stdin.readLine();
					try {
						index = Integer.parseInt(userCommand);
					}
					catch (NumberFormatException e) {
						System.out.println("Not a valid number.");
					}
					index--;
					if ((index < 0) || (index >= allConstants.size()))
						System.out.println("Not a valid selection. Enter a number from 1-"+(allConstants.size())+" inclusive.");
				}
				return (CycConstant)allConstants.get(index);
				
			}
			else
				return null;
		}
		else return null;

	}
	
	//helper for reading input => lists of CYC constants
	private void requestListOfConstantsforAnalogy(AbstractAnalogyManager managersList, CycConnection cycC) throws IOException
	{
		String userCommand = "";
		while(!userCommand.equals("stop"))
		{
			System.out.println("Enter Constant ('stop' to end) :> ");
			userCommand = stdin.readLine();
			while(!this.isValidSyntax(userCommand))
			{
				System.out.print("Constant :> ");
				userCommand = stdin.readLine();			
			}
			
			if (!userCommand.equals("stop"))
			{
				CycConstant result = findC(cycC, userCommand);
						
				if (result != null)					
					managersList.addConstant(result);																														
				else
					System.out.println("CYC Constant not found.");
			}
			else
				System.out.println("Done Reading Input.");
		}
	}

	private void demo1(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("Term to search for:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if (result != null)
			Log.current.println(result);
		else
			System.out.println("CYC Constant not found.");


	}

	private void demo2(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("Genls query:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if (result != null)
			Log.current.println("1st Level Genls: " + cycC.getGeneralizations(result));																								
		else
			System.out.println("CYC Constant not found.");		
	}
	private void demo3(CycConnection cycC, BufferedReader stdin) throws IOException
	{				
		System.out.print("Isas query:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}
		CycConstant result = findC(cycC, userCommand);
		if (result != null)
			Log.current.println("1st Level Isas: " + cycC.getIsa(result));
		else
			System.out.println("CYC Constant not found.");		
	}
	private void demo4(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("All Genls:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}
		CycConstant result = findC(cycC, userCommand);
		if ((result != null) && (cycC.getAllGenls(result)!= null))
		{			
			String allGenls = cycC.getAllGenls(result).toString();
			int numberofLevels = cycC.getAllGenls(result).getProperListSize();
			Log.current.println("All genls ("+numberofLevels+"): " + allGenls);					
		}
		else
			System.out.println("CYC Constant not found.");		
	}
	private void demo5(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("All GenlsPreds:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if (result != null)
			Log.current.println("All GenlsPreds: " + cycC.getAllGenlPreds(result));
		else
			System.out.println("CYC Constant not found.");		
	}
	private void demo6(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("All isa:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if ((result != null) && (cycC.getAllIsa(result) != null))
		{
			String allIsas = cycC.getAllIsa(result).toString();
			int numberofLevels = cycC.getAllIsa(result).getProperListSize();
			Log.current.println("All isa ("+numberofLevels+"): " + allIsas);

		} else
			System.out.println("CYC Constant not found.");

	}
	private void demo7(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("All DepSpec:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if (result != null)		
			Log.current.println("All Dep Spec: " + cycC.getDepSpecs(result));																									
		else
			System.out.println("CYC Constant not found.");

	}
	private void demo8(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("All SpecInv:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if (result != null)
			Log.current.println("All specInv: " + cycC.getSpecInvs(result));																							
		else
			System.out.println("CYC Constant not found.");		
	}
	private void demo9(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.print("Comments for constant:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		if (result != null)
			Log.current.println("Comments: " + cycC.getComment(result));
		else
			System.out.println("CYC Constant not found.");		
	}
	private void demo10(CycConnection cycC, BufferedReader stdin) throws IOException
	{		
		System.out.print("All levels beneath:\n> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("> ");
			userCommand = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);	
		if ((result != null) && (cycC.getAllGenls(result)!= null) && (cycC.getAllIsa(result) != null))
		{
			int levels =  cycC.getAllGenls(result).getProperListSize() + cycC.getAllIsa(result).getProperListSize();

			Log.current.println("All Levels beneath " + result+ ": " + levels);																						
		} 
		else
			System.out.println("CYC Constant not found.");

	}
	private void demo11(CycConnection cycC, BufferedReader stdin) throws IOException
	{
		System.out.print("Currently sorting by: ");
		System.out.print("#" + cycC.getSortBy() + " - " + CycConnection.reportSortBy(cycC.getSortBy()) + "\n");
		System.out.print("Enter a number for a new sorting priority:\n");	

		int index = 0;
		String sortName=CycConnection.reportSortBy(index);
		while (!sortName.equals(""))
		{
			System.out.print(index + " " + sortName + "\n");
			index++;
			sortName=CycConnection.reportSortBy(index);
		}		

		int sortIndex = requestIndex(stdin, index);
		Log.current.println("New sorting criterion: "+CycConnection.reportSortBy(cycC.setSortBy(sortIndex)) +".");        	
	}
	private void demo12(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.println("Categorical Similarity between two concepts:");
		System.out.print("Constant one:> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("Constant one:> ");
			userCommand = stdin.readLine();			
		}
		System.out.print("\nConstant two:> ");
		String userCommand1 = stdin.readLine();
		while(!this.isValidSyntax(userCommand1))
		{
			System.out.print("Constant two:> ");
			userCommand1 = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		CycConstant result1 = findC(cycC, userCommand1);


		if ((result != null) && (result1 != null)){
			AbstractElementPair pair = new AbstractElementPair(result, result1, cycC);
			pair.printCatSimScores();			
			Log.current.println("First Matches are: " + pair.getFirstMatches());
			Log.current.println("All matches: " + pair.getMatches());

		} else
			System.out.println("Failed to match due to CYC constant not found.");

	}
		
	private void demo13(CycConnection cycC, BufferedReader stdin)
	{		
		AbstractAnalogyManager manager = new AbstractAnalogyManager(cycC);		

		System.out.println("Categorical Similarity for all possible 1-to-1 mappings in Analog: ");		
		
		try {
			//call helper to request user input
			this.requestListOfConstantsforAnalogy(manager, cycC);
		} catch (IOException e) {
			System.out.println("Error reading from the input stream.");
			e.printStackTrace();
		}
		
		if (!manager.equals(null))
			manager.printAllPairScores();
		else
			System.out.println("Error - Data loss!");
	}
	private void demo14(CycConnection cycC, BufferedReader stdin)
	{		
		AbstractAnalogyManager manager = new AbstractAnalogyManager(cycC);

		System.out.println("Analogy Mapping using Categorical or Relational Similarity (set type of measure using option 11):");		
		try {
			//call helper to request user input
			this.requestListOfConstantsforAnalogy(manager, cycC);
		} catch (IOException e) {
			System.out.println("Error reading from the input stream.");
			e.printStackTrace();
		}
		if (!manager.equals(null))
			manager.printSimilarityMappedAnalogy();
		else
			System.out.println("Error - Data loss!");
	}
	private void demo15(CycConnection cycC, BufferedReader stdin)
	{		
		AbstractAnalogyManager manager = new AbstractAnalogyManager(cycC);

		System.out.println("SAT style Analogy question. Enter question pair followed by multiple choices (FIRST:SECOND::__:__):");		
		try {
			//call helper to request user input
			this.requestListOfConstantsforAnalogy(manager, cycC);
		} catch (IOException e) {
			System.out.println("Error reading from the input stream.");
			e.printStackTrace();
		}
		if (!manager.equals(null))
			manager.printSimMappedSATAnalogy();
		else
			System.out.println("Error - Data loss!");

	}
	private void demo16(CycConnection cycC, BufferedReader stdin)
	{		
		AbstractAnalogyManager manager = new AbstractAnalogyManager(cycC);

		System.out.println("Analogy Mapping from Direct Structure using Categorical Distance:");		
		try {			
			System.out.print("Enter elements of Analog 1:> ");
			//call helper to request user input
			this.requestListOfConstantsforAnalogy(manager, cycC);
			
			manager.addConstant(null);	//demarcate end of analog 1 and beginning of 2 (since analogs may be different sizes)
			System.out.print("Enter elements of Analog 2 :> ");
			//call helper to request user input
			this.requestListOfConstantsforAnalogy(manager, cycC);
			
		} catch (IOException e) {
			System.out.println("Error reading from the input stream.");
			e.printStackTrace();
		}
		if (!manager.equals(null))
			manager.printDirectStructureMappedAnalogy();
		else
			System.out.println("Error - Data loss!");
	}
	private void demo17(CycConnection cycC, BufferedReader stdin) throws IOException
	{
		System.out.print("Level of constant expansion is set to: ");
		System.out.print("#" + cycC.getExpandBy() + " - " + CycConnection.reportExpandBy(cycC.getExpandBy()) + "\n");
		System.out.print("Enter a number for a new expansion level:\n");	

		int index = 0;
		String expandName=CycConnection.reportExpandBy(index);
		while (!expandName.equals(""))
		{
			System.out.print(index + " " + expandName + "\n");
			index++;
			expandName=CycConnection.reportExpandBy(index);
		}		

		int expandIndex = requestIndex(stdin, index);
		Log.current.println("New expansion level: "+CycConnection.reportExpandBy(cycC.setExpandBy(expandIndex)) +".");
		if (cycC.getExpandBy()!=0)System.out.println("Warning: Expansion removes any duplicates! ");
	}
	private void demo18(CycConnection cycC, BufferedReader stdin) throws IOException
	{
		System.out.print("Currently calculating: ");
		System.out.print("#" + cycC.getRequiredScore() + " - " + CycConnection.reportRequiredScore(cycC.getRequiredScore()) + "\n");
		System.out.print("Currently sorting by: ");
		System.out.print("#" + cycC.getSortBy() + " - " + CycConnection.reportSortBy(cycC.getSortBy()) + "\n");
		System.out.print("Enter a number for a new score calculation priority:\n");	

		int index = 0;
		String calcName=CycConnection.reportRequiredScore(index);
		while (!calcName.equals(""))
		{
			System.out.print(index + " " + calcName + "\n");
			index++;
			calcName=CycConnection.reportRequiredScore(index);
		}		

		int calcIndex = requestIndex(stdin, index);
		Log.current.println("New calculation priority: "+CycConnection.reportRequiredScore(cycC.setRequiredScore(calcIndex)) +".");        	
	}
	private void demo19(CycConnection cycC, BufferedReader stdin) throws IOException
	{		

		System.out.println("Relational Similarity between two concepts:");
		System.out.print("Constant one:> ");
		String userCommand = stdin.readLine();
		while(!this.isValidSyntax(userCommand))
		{
			System.out.print("Constant one:> ");
			userCommand = stdin.readLine();			
		}
		System.out.print("\nConstant two:> ");
		String userCommand1 = stdin.readLine();
		while(!this.isValidSyntax(userCommand1))
		{
			System.out.print("Constant two:> ");
			userCommand1 = stdin.readLine();			
		}

		CycConstant result = findC(cycC, userCommand);
		CycConstant result1 = findC(cycC, userCommand1);


		if ((result != null) && (result1 != null)){
			AbstractElementPair pair = new AbstractElementPair(result, result1, cycC);
			pair.printRelSimScores();			

		} else
			System.out.println("Failed to match due to CYC constant not found.");

	}


	private int requestIndex(BufferedReader stdin, int maxIndex)//helper for select criterion methods
			throws IOException {
		int entryIndex = -1;				
		while ((entryIndex < 0) || (entryIndex >= maxIndex)) 
		{		
			System.out.print("> ");
			String userCommand = stdin.readLine();
			try {
				entryIndex = Integer.parseInt(userCommand);
			}
			catch (NumberFormatException e) {
				System.out.println("Not a valid number.");
			}
			if ((entryIndex < 0) || (entryIndex >= maxIndex))
				System.out.println("Not a valid criterion. Enter a number from 0-"+(maxIndex-1)+" inclusive.");
		}
		return entryIndex;
	}
}